package comm;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.LinkedBlockingQueue;

import comm.ServerSideMessage.Type;

/**
 * Communication interface for {@link moodish.client.MoodishClient Moodish clients}. Moodish clients must 
 * communicate with a MoodishServer exclusively through this interface. 
 * 
 * @author alc
 * @version 1
 */

public class ClientInterface implements ClientComm{
	// Class attributes

	private String myClientNickname;
	private SocketHandler socketHandler;
	private boolean isOnline = false;
	private InetAddress addr;

	//Queue that stores the messages that comes from the server
	private LinkedBlockingQueue<ClientSideMessage> toClientMessageQueue;
	/**
	 * Constructor that uses a queue to store messages.
	 */
	public ClientInterface(){
		toClientMessageQueue= new LinkedBlockingQueue<ClientSideMessage>();
	}
	/**
	 * Establish a connection to a Moodish server. This method should be the first one called by 
	 * the client as no other operation (expect for {@link #isConnected()}) can be performed before
	 * a connection to the server has been established.
	 * 
	 * @param host                    Address of the server. For instance "localhost", "192.168.1.1", and so on.
	 * @param nickname                The nickname to use for the client.
	 * @throws UnknownHostException   Thrown if the host cannot be found.
	 * @throws IOException            Thrown if a connection cannot be established.
	 */
	@Override
	public void connect(String host, String nickname) throws UnknownHostException, IOException {
		this.myClientNickname = nickname;
		addr = InetAddress.getByName(host);
		Socket socket = new Socket(addr, ServerInterface.PORTO);
		socketHandler = new SocketHandler(socket);

		socketHandler.start();
	}

	/**
	 * Check if currently connected to a host.
	 * 
	 * @return <b>true</b> if currently connected to a host, <b>false</b> otherwise.
	 */
	@Override
	public boolean isConnected() {
		return isOnline;
	}
	/**
	 * Disconnect from a Moodish server. Any sockets should be closed immediately. 
	 * Pending messages are not guaranteed to be delivered before disconnecting.
	 */	
	@Override
	public void disconnect() {
		ServerMessage serverMessage = new ServerMessage(myClientNickname, "", Type.CLIENT_DISCONNECTED);
		sendMenssageToServer(serverMessage);
	}
	/**
	 * Get the next message received from the server. If no message 
	 * is has been received, the method blocks until a message has 
	 * been received. If no message will ever be received (connection is down), null is returned.
	 * 
	 * @return The next message sent by the server to the client or null if no message will ever be received (disconnected from server).
	 */
	@Override
	public ClientSideMessage getNextMessage() {
		return toClientMessageQueue.poll();
	}
	/** 
	 * Check if a message from the server is pending. If {@link #hasNextMessage()} returns <b>true</b>, a call to {@link #getNextMessage()} 
	 * will return immediately with the oldest message in the queue. If {@link #hasNextMessage()} returns <b>false</b>, a call to {@link #getNextMessage()} 
	 * will block until a message has been received.
	 * 
	 * @return <b>true</b> if a message from the server is currently pending, otherwise <b>false</b>.
	 */
	@Override
	public boolean hasNextMessage() {
		return	!toClientMessageQueue.isEmpty();		
	}
	/**
	 * Send a Moodish message from this client to the server which should then relay the message to all freinds.
	 * 
	 * @param moodishMessage The Moodish message to be sent.
	 */
	@Override
	public void sendMoodishMessage(String moodishMessage) {
		ServerMessage newMoodishMessage = new ServerMessage(myClientNickname, moodishMessage, Type.MOODISH_MESSAGE );
		sendMenssageToServer(newMoodishMessage);
	}
	/**
	 * Request to be friend of another client.
	 * 
	 * @param nickname the nickname of the client that should be friend.
	 */
	@Override
	public void friendship(String nickname) {
		ServerMessage friendshipMessage = new ServerMessage(myClientNickname, nickname, Type.FRIENDSHIP);
		sendMenssageToServer(friendshipMessage);
	}
	/**
	 * Request to unfriend of  a client.
	 * 
	 * @param nickname the nickname of the client currently friend that should be unfriended. 
	 */
	@Override
	public void unfriendship(String nickname) {
		ServerMessage unfriendshipMessage = new ServerMessage(myClientNickname, nickname, Type.UNFRIENDSHIP);
		sendMenssageToServer(unfriendshipMessage);
	}



	/////////////////////////*** M�todo para enviar tudo para o servidor ***///////////////////////// 
	void sendMenssageToServer(ServerMessage m1){
		socketHandler.sendMenssage(m1);
	}
	////////////////////////////////////////////////////////////////////////////////////////////////



	//------------------------ START of SocketHandler -------------------------
	class SocketHandler extends Thread{
		private Socket socket;
		private ObjectInputStream in;
		private ObjectOutputStream out;
		private Boolean connected = true;


		public SocketHandler(Socket s){
			this.socket = s;

			try {
				//INIT STREAMS
				init();
			} catch (IOException e) {
				e.printStackTrace();
			}
			//Client informs Server that is connected
			ServerMessage IdentificationMessage = new ServerMessage(myClientNickname, "", Type.CLIENT_CONNECTED);
			sendMenssage(IdentificationMessage);

			System.out.println("CLIENT "+ this.getName()+" Handler created..!" + "myClientNickname="+myClientNickname);
		}
		/**
		 * Initializes streams.
		 * @throws IOException
		 */
		private void init() throws IOException {
			System.out.println("CLIENT " + this.getName() + " streams created!");
			out=new ObjectOutputStream(socket.getOutputStream());
			in=new ObjectInputStream(socket.getInputStream());

		}

		/**
		 * Sends a message to the server from the Client
		 */
		synchronized void  sendMenssage(ServerMessage message){
			try {
				out.writeObject(message);
				out.reset();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}


		public void run(){	
			ClientMessage clientMessage;
			//Enquanto esta Thread Client estiver conectada
			while(connected){
				System.out.println("CLIENT " + " " + getCurrentClientName() + "  waiting for msgs....");
				try {
					//Vai buscar as mensagens enviadas pelo servidor
					clientMessage = (ClientMessage) in.readObject();

					//Se a msg for do tipo DISCONNECTED e sender for SERVIDOR é porque o SERVIDOR quer desligar-me
					if((clientMessage.getType() == ClientSideMessage.Type.DISCONNECTED) 
							&& clientMessage.getSendersNickname().equals(ClientSideMessage.SERVER)
							&& clientMessage.getPayload().equals(myClientNickname)){
						logOff();
					}
					//Se a msg for do tipo CONNECTED e o sender for SERVIDOR é porque vou ligar-me
					else if((clientMessage.getType() == ClientSideMessage.Type.CONNECTED)
							&& clientMessage.getSendersNickname().equals(ClientSideMessage.SERVER)
							&& clientMessage.getPayload().equals(myClientNickname)){
						isOnline = true;
						System.out.println("CLIENT " +this.getName() + "  " + getCurrentClientName() + " is connected!");
					}
					//Senão é porque a mensagem é de outro tipo, adicionar à fila
					else{
						toClientMessageQueue.add(clientMessage);
					}
				} catch (IOException e) {
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				}
			}
		}

		///////////////////****Disconecta o cliente***///////////////////////////////
		/**
		 * Disconnects client.
		 */
		private void logOff() {
			isOnline = false;
			connected = false;
			try {
				System.out.println("CLIENT " + this.getName() + " closing streams and socket...!");
				in.close();
				out.close();
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		/////////////////////////////////////////////////////////////////////////////
	}
	//------------------------ END of SocketHandler -------------------------


	//TODO METODOS NOVOS QUE FORAM ADICIONADOS POR MIM PARA DESPISTAR A APLICACAO

	//	public String getClientInfo(){
	//		return "SocketHandler: " + socketHandler.toString() + " cliente: " + this.myClientNickname;
	//	}
	//
	/**
	 * Inspects the current client name that is connected to Host.
	 */
	private String getCurrentClientName(){
		return this.myClientNickname;
	}
}
